<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page isELIgnored="false" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<title>SketchCast - Chapter 9</title>
	<link rel="stylesheet" href="/strophe/public/css/jquery-ui-1.8.21.custom.css">
	<link rel="stylesheet" href="/strophe/public/css/sketchcast.css">
	<script type="text/javascript" src="/strophe/public/js/jquery-1.7.2.min.js"></script>
	<script type="text/javascript" src="/strophe/public/js/jquery-ui-1.8.21.custom.min.js"></script>
	<script type="text/javascript" src="/strophe/public/js/strophe.js"></script>
	<script type="text/javascript" src="/strophe/public/js/flXHR.js"></script>
	<script type="text/javascript" src="/strophe/public/js/strophe.flxhr.js"></script>
	<script type="text/javascript">
		var SketchCast = {
			penDown: false,
			oldPos: null,
			color: '000',
			lineWidth: 4,
			
			connection: null,
			service: null,
			node: null,
			subid: null,
			
			NS_DATA_FORMS: "jabber:x:data",
			NS_PUBSUB: "http://jabber.org/protocol/pubsub",
			NS_PUBSUB_OWNER: "http://jabber.org/protocol/pubsub#owner",
			NS_PUBSUB_ERRORS: "http://jabber.org/protocol/pubsub#errors",
			NS_PUBSUB_NODE_CONFIG: "http://jabber.org/protocol/pubsub#node_config",
			
			onEvent: function(message) {
				if ($(message).find('x').length > 0) {
					var color = $(message).find('field[var="color"] value').text();
					var lineWidth = $(message).find('field[var="lineWidth"] value').text();
					var from = $(message).find('field[var="from_pos"] value').text().split(",");
					var to = $(message).find('field[var="to_pos"] value').text().split(",");
					
					// build an action object
					var action = {
						color: color,
						lineWidth: lineWidth,
						from: {x: parseFloat(from[0]), y: parseFloat(from[1])},
						to: {x: parseFloat(to[0]), y: parseFloat(to[1])}
					};
					
					SketchCast.render(action);
				}				
// 				else if ($(message).find('delete[node="' + SketchCast.node + '"]').length > 0) {
// 					SketchCast.showError("SketchCast ended by presenter.");
// 				}
				else {
					$('#status').html('other message received.');	
				}	
				
				return true;
			},
			
			onOldItems: function(iq) {
				$(iq).find('item').each(function() {
					SketchCast.onEvent(this);
				});
			},
			
			onOldItemError: function(iq) {
				$('#status').html("Getting missed items failed with " + SketchCast.makeErrorFromIQ(iq));
				SketchCast.showError("Getting missed items failed with " + SketchCast.makeErrorFromIQ(iq));
			},
			
			onSubscribed: function(iq) {
				// retrieve subid
				SketchCast.subid = $(iq).find('subscription').attr('subid');
				
				$(document).trigger("reception_started");
			},
			onSubscribeError: function(iq) {
				SketchCast.showError("Subscribed failed with " + SketchCast.makeErrorFromIQ(iq));
			},
			
			onCreated: function(iq) {
				// find pubsub node
				var node = $(iq).find('create').attr('node');
				SketchCast.node = node;
				
				// configure the node
				var configiq = $iq({to: SketchCast.service, type: "set"})
						.c('pubsub', {xmlns: SketchCast.NS_PUBSUB_OWNER})
						.c('configure', {node: node})
						.c('x', {xmlns: SketchCast.NS_DATA_FORMS, type: "submit"})
						.c('field', {"var": "FORM_TYPE"})
						.c('value').t(SketchCast.NS_PUBSUB_NODE_CONFIG)
						.up().up()
						.c('field', {"var": "pubsub#delivery_payloads"})
						.c('value').t("1")
						.up().up()
						// Openfire does not support send_last_published_item property. Instead of it, use send_item_subscribe property
// 						.c('field', {"var": "pubsub#send_last_published_item"})
// 						.c('value').t("never")
// 						.up().up()
						.c('field', {"var": "pubsub#send_item_subscribe"})
						.c('value').t("0")
						.up().up()
						.c('field', {"var": "pubsub#persist_items"})
						.c('value').t("1")
						.up().up()
						.c('field', {"var": "pubsub#max_items"})
						.c('value').t("100");
				SketchCast.connection.sendIQ(configiq, SketchCast.onConfigured, SketchCast.onConfigureError);
			},
			onCreateError: function(iq) {
				SketchCast.showError("SketchCast creation failed with " + SketchCast.makeErrorFromIQ(iq));
			},
			
			onConfigured: function(iq) {
				$(document).trigger("broadcast_started");
			},
			onConfigureError: function(iq) {
				SketchCast.showError("SketchCast configuration failed with " + SketchCast.makeErrorFromIQ(iq));
			},
			
			publishAction: function(action) {
				SketchCast.connection.sendIQ(
					$iq({to: SketchCast.service, type: "set"})
					.c('pubsub', {xmlns: SketchCast.NS_PUBSUB})
					.c('publish', {node: SketchCast.node})
					.c('item')
					.c('x', {xmlns: SketchCast.NS_DATA_FORMS, type: "result"})
					.c('field', {"var": "color"})
					.c('value').t('' + action.color)
					.up().up()
					.c('field', {"var": "lineWidth"})
					.c('value').t('' + action.lineWidth)
					.up().up()
					.c('field', {"var": "from_pos"})
					.c('value').t('' + action.from.x + "," + action.from.y)
					.up().up()
					.c('field', {"var": "to_pos"})
					.c('value').t('' + action.to.x + "," + action.to.y)
				);				
			},
			
			render: function(action) {
				// render the line segment
				var ctx = $('#sketch').get(0).getContext('2d');
				ctx.strokeStyle = '#' + action.color;
				ctx.lineWidth = action.lineWidth;
				ctx.beginPath();
				ctx.moveTo(action.from.x, action.from.y);
				ctx.lineTo(action.to.x, action.to.y);
				ctx.stroke();
			},
			
			makeErrorFromIQ: function(iq) {
				var error = $(iq).find('*[xmlns="' + Strophe.NS.STANZAS + '"]').get(0).tagName;
				var pubsub_error = $(iq).find('*[xmlns="' + SketchCast.NS_PUBSUB_ERRORS + '"]');
				
				if (pubsub_error.length > 0) {
					error = error + "/" + pubsub_error.get(0).tagName;
				}
				
				return error;
			},
			
			showError: function(message) {
				SketchCast.disconnect();
				
				$('#error_dialog p').text(message);
				$('#error_dialog').dialog('open');
			},
			
			disconnect: function() {
				$('#erase').click();
				
				SketchCast.connection.disconnect();
				SketchCast.connection = null;
				SketchCast.service = null;
				SketchCast.node = null;
				SketchCast.subid = null;
			},
			
			activate: function() {
				$('.button').removeClass('disabled');
				$('#sketch').removeClass('disabled');
				$('#erase').removeAttr('disabled');
				$('#disconnect').removeAttr('disabled');
			},
			
			reset: function() {
				$('.button').addClass('disabled');
				$('#sketch').addClass('disabled');
				$('#erase').attr('disabled', 'disabled');
				$('#disconnect').attr('disabled', 'disabled');
			}
		};
		
		$(document).ready(function() {
			$('#sketch').mousedown(function() {
				SketchCast.penDown = true;
			});
			
			$('#sketch').mouseup(function() {
				SketchCast.penDown = false;
			})
			
			$('#sketch').mousemove(function(ev) {
				// get the position of the drawing area, our offset
				var offset = $(this).offset();
				
				// calculate our position within the drawing area
				var pos = {
					x: ev.pageX - offset.left, y: ev.pageY - offset.top	
				};
				
				if (SketchCast.penDown) {
					if (!SketchCast.oldPos) {
						SketchCast.oldPos = pos;
						return;
					}
					
					if (!$('#sketch').hasClass('disabled') &&
						(Math.abs(pos.x - SketchCast.oldPos.x) > 2 || Math.abs(pos.y - SketchCast.oldPos.y) > 2)) {
						
						SketchCast.render({
							color: SketchCast.color,
							lineWidth: SketchCast.lineWidth,
							from: {x: SketchCast.oldPos.x, y: SketchCast.oldPos.y},
							to: {x: pos.x, y: pos.y}
						});
												
						SketchCast.publishAction({
							color: SketchCast.color,
							lineWidth: SketchCast.lineWidth,
							from: SketchCast.oldPos,
							to: pos
						});
					}					
					
					SketchCast.oldPos = pos;
				}
				else {
					SketchCast.oldPos = null;
				}
			});
			
			$('.color').click(function(ev) {
				SketchCast.color = $(this).attr('id').split('-')[1];
			});
			
			$('.linew').click(function(ev) {
				SketchCast.lineWidth = $(this).attr('id').split('-')[1];
			});
			
			$('#erase').click(function(ev) {
				var ctx = $('#sketch').get(0).getContext('2d');
				ctx.fillStyle = '#fff';
				ctx.strokeStyle = '#fff';
				ctx.fillRect(0, 0, 600, 500);
			});
			
// 			$('#disconnect').click(function(ev) {
// 				SketchCast.connection.disconnect();
// 			});
			
			// configure login dialog
			$('#login_dialog').dialog({
				autoOpen: true,
				draggable: false,
				modal: true,
				title: 'Connect to a SketchCast',
				buttons: {
					"Connect" : function() {
						$(document).trigger('connect', {
							jid: $('#jid').val(),
							password: $('#password').val(),
							service: $('#service').val(),
							node: $('#node').val()
						});
						
// 						$('#password').val('');
						$(this).dialog('close');
					}
				}
			});
			
			// configure error dialog
			$('#error_dialog').dialog({
				autoOpen: false,
				draggable: false,
				modal: true,
				title: "Whoops! Something Bad Happend!",
				buttons: {
					"Ok": function() {
						$(this).dialog('close');
						$('#login_dialog').dialog('open');
					}
				}
			});
		});
		
		// connect event handler
		$(document).bind('connect', function(ev, data) {
			$('#status').html('Connecting...');
			
			var conn = new Strophe.Connection("${config.boshUrl}");
			conn.connect(data.jid, data.password, function(status) {
				if (status == Strophe.Status.CONNECTED) {
					$(document).trigger('connected');
				}
				else if (status == Strophe.Status.DISCONNECTED) {
					$(document).trigger('disconnected');
				}
			});
			
			SketchCast.connection = conn;
			SketchCast.service = data.service;
			SketchCast.node = data.node;
		});
		
		// connected event handler
		$(document).bind('connected', function() {
			$('#status').html('Connected.');
			
			// send a negative presence since we're not a chat client
			SketchCast.connection.send($pres().c("priority").t("-1"));
			
			if (SketchCast.node.length > 0) {
				// a node was specified, so we attempt to subscribe to it
				
				// first, set up a callback for the events
				SketchCast.connection.addHandler(SketchCast.onEvent, null, "message", null, null, SketchCast.service);
				
				// now subscribe
				var subiq = $iq({to: SketchCast.service, type: "set"})
						.c('pubsub', {xmlns: SketchCast.NS_PUBSUB})
						.c('subscribe', {node: SketchCast.node, jid: SketchCast.connection.jid});
				SketchCast.connection.sendIQ(subiq, SketchCast.onSubscribed, SketchCast.onSubscribeError);
			}
			else {
				// a node was not specified, so we start a new sketchcast
				var createiq = $iq({to: SketchCast.service, type: "set"})
						.c('pubsub', {xmlns: SketchCast.NS_PUBSUB})
						.c('create');
				SketchCast.connection.sendIQ(createiq, SketchCast.onCreated, SketchCast.onCreateError);
			}
		});
		
		// disconnected event handler		
		$(document).bind('disconnected', function() {
			$('#status').html('disconnected.');
		});
		
		// broadcast start event handler
		$(document).bind('broadcast_started', function() {
			$('#status').html(
				'Broadcasting at service: <i>' + SketchCast.service + '</i> ' +
				'node: <i>' + SketchCast.node + '</i>'
			);
			
			SketchCast.activate();
			$('#disconnect').click(function() {
				SketchCast.reset();
				
				// delete the pubsub node
				SketchCast.connection.sendIQ(
					$iq({to: SketchCast.service, type: "set"})
						.c('pubsub', {xmlns: SketchCast.NS_PUBSUB_OWNER})
						.c('delete', {node: SketchCast.node})
				);
				
				$('#status').html('disconnecting...');
				
				SketchCast.disconnect();
				$('#login_dialog').dialog('open');
			});
		});
		
		// reception started event handler
		$(document).bind('reception_started', function() {
			$('#status').html("Receiving SketchCast.");
			$('#disconnect').removeAttr('disabled');
			
			$('#disconnect').click(function() {
				$('#disconnect').attr('disabled', 'disabled');
				
				// unsubscribe the pubsub node
				SketchCast.connection.sendIQ(
					$iq({to: SketchCast.service, type: "set"})
						.c('pubsub', {xmlns: SketchCast.NS_PUBSUB})
						.c('unubscribe', {node: SketchCast.node, jid: SketchCast.connection.jid})
				);
				
				$('#status').html('disconnecting...');
				
				SketchCast.disconnect();
				$('#login_dialog').dialog('open');
			});
			
			// get missed events
			SketchCast.connection.sendIQ(
				$iq({to: SketchCast.service, type: "get"})
					.c('pubsub', {xmlns: SketchCast.NS_PUBSUB})
					.c('items', {node: SketchCast.node, subid: SketchCast.subid}),
				SketchCast.onOldItems,
				SketchCast.onOldItemError
			);
		});		
	</script>
</head>
<body>
	<h1>SketchCast</h1>
	
	<!-- status bar -->
	<div id="status"></div>
	
	<!-- drawing tool buttons. The ids are the button values -->
	<div class='button disabled color' id='color-000'></div>
	<div class='button disabled color' id='color-f00'></div>
	<div class='button disabled color' id='color-0f0'></div>
	<div class='button disabled color' id='color-00f'></div>
	<div class='button disabled linew' id='width-2'><div></div></div>
	<div class='button disabled linew' id='width-4'><div></div></div>
	<input type='button' id='erase' value='erase sketch' disabled>
	<input type='button' id='disconnect' value='disconnect' disabled>
	<div class='clear'></div>
	
	<!-- drawing area -->
	<canvas id='sketch' class='disabled' width='600' height='500'></canvas>
	
	<!-- login dialog -->
	<div id='login_dialog' class='hidden'>
		<label>JID:</label><input type='text' id='jid' value='sejoonlim@${config.xmppDomain}'><br>
		<label>Password:</label><input type='password' id='password' value='welcome1'><br>
		<label>Pubsub service:</label><input type='text' id='service' value='pubsub.${config.xmppDomain}'><br>
		<label>Pubsub node:</label><input type='text' id='node'>
	</div>
	
	<!-- empty error dialog -->
	<div id='error_dialog' class='hidden'>
		<p></p>
	</div>
</body>
</html>